package cn.edu.dgut.css.sai.demo.remoteloadjar.local;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.config.ConfigFileApplicationListener;
import org.springframework.boot.context.properties.bind.PropertySourcesPlaceholdersResolver;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.loader.LaunchedURLClassLoader;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author Sai
 * Created by Sai on 2020/6/8.
 */
@SuppressWarnings("JavadocReference")
@SpringBootApplication
public class LocalApplicationDemo {

    static {
        RemoteClassLoader.init();
    }

    /**
     * Spring-Boot应用程序-远程加载属性文件的设计与实现
     * <p></p>
     * 断点调试{@link ConfigFileApplicationListener}
     * <p></p>
     * Spring-boot应用程序加载属性文件到{@link Environment}的工作是由{@link ConfigFileApplicationListener}完成的，它比较特殊，它既是{@link ApplicationListener}又是{@link EnvironmentPostProcessor}。<br/>
     * {@link EnvironmentPostProcessor}是可以扩展{@link Environment} ，例如，利用这个扩展点 修改、添加属性，读取外部属性文件等。<br/>
     * {@link ConfigFileApplicationListener#loadPostProcessors()}从这个方法的源码可知，ClassLoader形参的传值是getClass().getClassLoader(),即{@link LaunchedURLClassLoader},它只能读取Fat Jar里面的配置的{@link EnvironmentPostProcessor}；<br/>
     * 同时，从{@link SpringFactoriesLoader#loadFactories(java.lang.Class, ClassLoader)}源码知道，在实例化{@link EnvironmentPostProcessor}的时候也是用相同的{@link LaunchedURLClassLoader} <br/>
     * {@link ConfigFileApplicationListener}本身也是一个{@link EnvironmentPostProcessor}，在{@link ConfigFileApplicationListener#postProcessEnvironment(ConfigurableEnvironment, SpringApplication)}方法中加载指定位置上的application-*名称的属性文件。<br/>
     * 从{@link ConfigFileApplicationListener.Loader#Loader(ConfigurableEnvironment, ResourceLoader)}的源码可知，如果不配置{@link SpringApplication#resourceLoader}(默认就是null)，会使用{@code getClass().getClassLoader()}读取属性文件，从上面分析就是{@link LaunchedURLClassLoader} <br/>
     * 因此得出结论：{@link ConfigFileApplicationListener}默认配置下只会读取spring-boot应用程序本身的Fat Jar中的属性文件；<br/>
     * 如果想要读取外部属性文件或远程文件，需要配置{@link SpringApplication#resourceLoader},而{@link ResourceLoader}是利用{@link ClassLoader}加载属性文件的,<br/>
     * 所以{@link ResourceLoader}要实现加载外部的属性文件，需要有一个能加载外部文件的{@link ClassLoader},如：{@link java.net.URLClassLoader}<br/>
     * <p></p>
     * spring-boot框架内部有一个{@link ResourceLoader}的默认实现{@link DefaultResourceLoader}，@link ConfigFileApplicationListener}也是默认使用这个。<br/>
     * 自定义一个可以加载外部文件的{@link ClassLoader}，然后利用构造函数{@link DefaultResourceLoader#DefaultResourceLoader(ClassLoader)}或{@link DefaultResourceLoader#setClassLoader(ClassLoader)}setter方法配置给{@link DefaultResourceLoader},<br/>
     * 这样自定义的{@link ResourceLoader}就可以读取外部属性文件，从而{@link ConfigFileApplicationListener}可以加载外部属性文件，并扩展Spring-Boot应用程序的{@link Environment}！<br/>
     * 注意：Spring-Boot应用程序是启动、初始化时，都是从{@link Environment}读取属性值的。
     *
     * <p></p>
     * @see ResourceLoader
     * @see ConfigFileApplicationListener
     * @see ConfigFileApplicationListener.Loader#load()
     * @see PropertySourcesPlaceholdersResolver
     * @see DefaultResourceLoader
     * @see ClassUtils#getDefaultClassLoader()
     */
    public static void main(String[] args) {
//        SpringApplication.run(LocalApplicationDemo.class, args);
        new SpringApplication(new DefaultResourceLoader(Thread.currentThread().getContextClassLoader()), LocalApplicationDemo.class).run(args);
    }

    @RestController
    static class HelloController {

        final
        Environment ev;

        public HelloController(Environment ev) {
            this.ev = ev;
        }

        // http://localhost:8080/sayHello
        @GetMapping("/sayHello")
        public String sayHello() {
            return "local-application";
        }

        // http://localhost:8080/localValue
        @GetMapping("/localValue")
        public String localProperties() {
            return ev.getProperty("sai.local.test");
        }

        // http://localhost:8080/remoteValue
        @GetMapping("/remoteValue")
        public String remoteProperties() {
            return ev.getProperty("sai.remote.test");
        }
    }

    // 测试远程jar自动配置的Controller
    // http://localhost:8080/remote
}
